浅谈 MVVM 和 Vue

Author Avatar
Klein 8月 24, 2018

使用 jQuery 和使用其他框架的区别

jQuery 实现 todo-list

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<div>
<input type="text" id="txt-title">
<button id="btn-submit">submit</button>
</div>
<div>
<ul id="ul-list"></ul>
</div>

<script type="text/javascript" scr="./jquery-3.2.1.js"></script>
<script type="text/javascript">
var $txtTitle = $('#txt-title')
var $ulList = $('#ul-list')
var $btnSubmit = $('#bth-submit')
$btnSubmit.click(function () {
var title = $txtTitle.val()
if (!title) {
return
}
var $li = $('<li>' + title + '</li>')
$ulList.append($li)
$txtTitle.val('')
})
</script>

Vue 实现 todo-list

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<div id="app">
<div>
<input v-model="title">
<button :click="add">submit</button>
</div>
<ul id="ul-list">
<li v-for="item in list">{{item}}</li>
</ul>
</div>


<script src="https://cdn.jsdelivr.net/npm/vue@2.5.17/dist/vue.js"></script>
<script type="text/javascript">
var vm = new Vue({
el: '#app',
data: {
title: '',
list: []
},
methods: {
add: function () {
this.list.push(this.title)
this.title = ''
}
}
})
</script>

jQuery 和 Vue 的区别

  • 数据和视图的分离,解耦(开放封闭原则)

在 jQuery 的例子中,li 元素通过 DOM 操作添加到 ul 元素中,逻辑和视图混在了一起,数据和视图也就没有分离。不符合封闭开放原则。

在 Vue 的例子中,数据和视图分离。

  • 以数据驱动视图

在 jQuery 的例子中,直接通过 DOM 操作修改视图。

在 Vue 的例子中,通过修改数据来改变视图。如何改变视图,如何渲染,我们并不关心,我们只关心数据变化,因为 DOM 操作已经被封装。

对 MVVM 的理解

  • MVC

    • Model 模型,数据
    • View 视图,模板
    • Controller 控制器,逻辑处理
  • MVVM

    • Model 模型,数据
    • View 视图,模板
    • ViewModel 连接视图和模型的桥梁

MVVM 是 MVC 的微创新

MVVM 的三大要素

数据响应:Vue 如何监听到 data 的每个属性变化

通过 Object.defineProperty 监听对象的获取和变化

1
2
3
4
5
var obj = {
name: 'zhangsan',
age: 26
}
console.log(obj);

上面的例子中,通过对象字面量的形式无法监听对象的获取和变化。

1
2
3
4
5
6
7
8
9
10
11
12
var obj = {}
var _name = 'wangwu'
Object.defineProperty(obj, 'name', {
get: function () {
console.log('get', _name); // 监听
return _name
},
set: function (newVal) {
console.log('set', newVal); // 监听
_name = newVal
}
})

上面的例子中,通过 Object.defineProperty 可以监听到对象的获取和变化。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
var vm = {}
var data = {
name: 'wangwu',
age: 26
}
var key, value

for (key in data) {
(function (key) {
Object.defineProperty(vm, key, {
get: function () {
console.log('get', data[key]); // 监听
return data[key]
},
set: function (newVal) {
console.log('set', newVal); // 监听
data[key] = newVal
}
})
})(key)
}

上面的代码模拟 Vue 的内部实现:利用 Object.defineProperty 监听到对象的获取和变化以及模拟 data 属性代理到 Vue 实例 vm

模板解析: vue 的模板如何被解析,指令如何处理

  • 模板是什么
    • 模板的本质是字符串。
    • 有逻辑。 如v-if v-for
    • 与 html 格式很像,但是有很大区别
    • 最终要转换为 html 来显示
    • 模板最终必须转换成 JS 代码,因为:
      • 有逻辑(v-if v-for)必须用 JS 才能实现(图灵完备)。
      • 转换为 html 渲染页面,必须用 JS 才能实现
      • 模板最重要转换成一个 JS 函数(render 函数)
        1
        2
        3
        4
        5
        6
        7
        8
        9
        <div id="app">
        <div>
        <input v-model="title">
        <button :click="add">submit</button>
        </div>
        <ul id="ul-list">
        <li v-for="item in list">{{item}}</li>
        </ul>
        </div>
  • render 函数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    <div id="app">
    <p>{{price}}</p>
    </div>

    <script type="text/javascript">
    var vm = new Vue({
    el: '#app',
    data: {
    price: 100
    }
    })

    // 以下是手写的 ppt 中的 render 函数
    function render() {
    // this 就是 vm
    with(this) {
    return _c(
    'div',
    {
    attrs: {'id': 'app'}
    },
    [
    _c('p', [_v(_s(price))])
    ]
    )
    }
    }
    // 上面的 render 函数相当于下面的 render 函数
    function render1() {
    return vm._c(
    'div',
    {
    attrs: {'id': 'app'}
    },
    [
    vm._c('p', [vm._v(vm._s(vm.price))])
    ]
    )
    }

    </script>
  • render 函数 与 vdom

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    vm._update(vnode) {
    const prevVnode = vm.vnode
    vm.vnode = vnode
    if (!prevVnode) {
    vm.$el = vm.__patch__(vm.$el, vnode)
    } else {
    vm.$el = vm.__patch__(prevVnode, vnode)
    }
    }

    function updateComponent () {
    // vm._render 即 render 函数,返回 vnode
    vm._update(vm._render())
    }
  • updateComponent 实现了 vdom 的 patch

  • 页面首次渲染执行了 updateComponent
  • data 中每次修改属性都会执行 updateComponent

渲染: vue 的模板如何被渲染成 html ? 以及渲染过程

  1. 解析模板成 render 函数
  • 模板中的所有信息都被 render 函数包含
  • 模板用到的 data 中的属性,都变成了 JS 变量
  • 模板中的 v-modelv-forv-on 都变成了 JS 逻辑
  • render 函数返回 vnode
  1. 数据响应开始监听
  • Object.defineProperty
  • 将 data 的属性代理到 Vue 实例
  1. 首次渲染,显示页面,且绑定依赖。
  • 初次渲染,执行 updateComponent ,执行 vm._render()
  • 执行 render 函数,会访问到被 Vue 实例代理的 data 的属性
  • 会被数据响应的Object.defineProperty的 get 方法监听到(只监听需要用的的属性,没有用到的不关心,避免不必要的重复渲染)
  • 执行 updateComponent,会走 vdom 的 patch 方法
  • patch 将 vnode 渲染成 DOM ,初次渲染完成
  1. data 属性变化,触发 rerender
  • 修改属性,会被数据响应的Object.defineProperty的 set 方法监听到
  • set 方法中执行 updateComponent
  • updateComponent 重新执行 vm.render
  • 生成 vnode 和 prevVnode ,通过 patch 进行比较
  • 渲染到 html